package com.example.springbootdemo.task;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.LineHandler;
import cn.hutool.core.util.RuntimeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.extra.mail.MailUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * TODO
 *
 * @author ：f4cklangzi@gmail.com
 * @date ：Created At 2023/11/21
 */
@Slf4j
@Component
public class AutoUpload {
    private static final String EXEC = "/mnt/BaiduPCS-Go-v3.9.7-linux-amd64/BaiduPCS-Go";
    private static final String LOCAL_PATH = "/mnt/xiaomi_camera_videos/78DF722E3D2D/";
    private static final String REMOTE_PATH = "/NAS/xiaomi_camera_videos/78DF722E3D2D/";
    private static final String PYTHON = "/www/server/panel/pyenv/bin/python";
    private static final String CHECK_VIDEO_PY = "/mnt/check_video.py";
    
    private static final Set<String> UPLOAD_FILES = new HashSet<>();
    private static final File RECORD_FILE = new File("/mnt/files.txt");
    
    private static final int MAX_LOCAL_FILE_COUNT = 10000;
    
    @Resource
    private ConfigurableApplicationContext applicationContext;
    
    private static boolean checkStatic(String videoPath) {
        log.info("文件[{}]开始检查画面...", videoPath);
        String cmd = PYTHON + " " + CHECK_VIDEO_PY + " " + videoPath;
        long start = System.currentTimeMillis();
        Process process = RuntimeUtil.exec(cmd);
        AtomicReference<String> lastLine = new AtomicReference<>();
        IoUtil.readUtf8Lines(process.getInputStream(), (LineHandler) line -> {
            log.info(line);
            lastLine.set(line);
        });
        long end = System.currentTimeMillis();
        boolean isStatic = lastLine.get().contains("FALSE");
        log.info("文件[{}]检查画面完成:[isStatic:{}],耗时:{}ms", videoPath, isStatic, end - start);
        return isStatic;
    }
    
    @SneakyThrows
    public static List<String> backup(List<String> pathList) {
        // umount();
        // mount();
        List<String> res = new ArrayList<>();
        for (String path : pathList) {
            String cmd = EXEC + " d " + path;
            log.info("备份[{}]:[{}]", path, cmd);
            Process process = RuntimeUtil.exec(cmd);
            IoUtil.readUtf8Lines(process.getInputStream(), (LineHandler) line -> {
                if (line.startsWith("下载结束")) {
                    log.info("[{}]:{}", path, line);
                    res.add(line);
                }
            });
        }
        TimeUnit.SECONDS.sleep(30);
        // umount();
        return res;
    }
    
    public static void umount() {
        List<String> list = RuntimeUtil.execForLines(StandardCharsets.UTF_8, "mount -l");
        for (String line : list) {
            if (line.contains("/mnt/disk4t")) {
                String diskName = line.split(" ")[0];
                String cmd = "umount " + diskName;
                log.info("卸载:[{}]", cmd);
                String res = RuntimeUtil.execForStr("umount " + diskName);
                if (StrUtil.isNotBlank(res)) {
                    sendMail("卸载磁盘失败", res);
                }
            }
        }
    }
    
    public static void mount() {
        List<String> list = RuntimeUtil.execForLines(StandardCharsets.UTF_8, "fdisk -l");
        for (String line : list) {
            if (line.contains("3.64")) {
                String diskName = StrUtil.subBetween(line, "Disk ", ":");
                String cmd = "mount " + diskName + " /mnt/disk4t";
                log.info("挂载:[{}]", cmd);
                String res = RuntimeUtil.execForStr("mount " + diskName + " /mnt/disk4t");
                if (StrUtil.isNotBlank(res)) {
                    sendMail("挂载磁盘失败", res);
                }
            }
        }
    }
    
    private static void zip(String videoPath, String zipPath) {
        log.info("文件[{}]开始压缩[{}]...", videoPath, zipPath);
        String[] split = videoPath.split("/");
        String fileName = split[split.length - 1];
        String password = SecureUtil.md5(fileName);
        log.info("密码:[{}]-[{}]", fileName, password);
        String cmd = "zip -jqu1 -P " + password + " " + zipPath + " " + videoPath;
        long start = System.currentTimeMillis();
        RuntimeUtil.execForStr(cmd);
        long end = System.currentTimeMillis();
        log.info("文件[{}]压缩完成[{}],耗时:{}ms", videoPath, zipPath, end - start);
    }
    
    private static void handleAllFile(String path, Consumer<String> consumer) {
        String cmd = EXEC + " ls " + path;
        System.out.println("执行:" + cmd);
        List<String> list = RuntimeUtil.execForLines(cmd);
        for (String line : list) {
            String[] split = line.split("\\s+");
            if (split.length == 6) {
                String item = path + split[split.length - 1];
                if ("-".equals(split[2])) {
                    handleAllFile(item, consumer);
                } else {
                    consumer.accept(item);
                }
            }
        }
    }
    
    private static void sendMail(String title, String content) {
        MailUtil.send("1120704214@qq.com", title, content, false, new File("/www/wwwlogs/java/app.log"));
    }
    
    @Scheduled(fixedDelay = 1000 * 60)
    public void run() {
        List<String> fileNames = FileUtil.listFileNames(LOCAL_PATH);
        // int fileCount = fileNames.size();
        // if (fileCount > MAX_LOCAL_FILE_COUNT) {
        //     fileNames.stream().sorted(String::compareTo).limit(fileCount - MAX_LOCAL_FILE_COUNT).forEach(fileName
        //     -> {
        //         log.info("删除本地文件:" + fileName);
        //         FileUtil.del(LOCAL_PATH + fileName);
        //     });
        // }
        fileNames = fileNames.stream().sorted(String::compareTo).collect(Collectors.toList());
        for (String fileName : fileNames) {
            String filePath = LOCAL_PATH + fileName;
            if (!StrUtil.endWith(fileName, ".mp4")) {
                continue;
            }
            if (UPLOAD_FILES.contains(fileName)) {
                continue;
            }
            long size = FileUtil.size(new File(filePath));
            if (size != 134217728) {
                log.info("文件[{}]当前大小:{} != 134217728,等待文件继续写入...", fileName, size);
                continue;
            }
            // 检查画面状态
            boolean isStatic = checkStatic(filePath);
            // 压缩加密
            String zipPath = filePath.replace(".mp4", "");
            if (isStatic) {
                zipPath += "_static";
            }
            zipPath += ".zip";
            zip(filePath, zipPath);
            String fileYear = fileName.substring(0, 4);
            String fileMonth = fileName.substring(4, 6);
            String fileDate = fileName.substring(6, 8);
            String remoteDatePath = REMOTE_PATH + fileYear + "/" + fileMonth + "/" + fileDate + "/";
            String uploadCmd = EXEC + " upload " + zipPath + " " + remoteDatePath;
            Process process = RuntimeUtil.exec(uploadCmd);
            log.info("文件[{}]开始上传至[{}]...", fileName, remoteDatePath);
            List<String> lines = new ArrayList<>();
            long startTime = System.currentTimeMillis();
            IoUtil.readUtf8Lines(process.getInputStream(), (LineHandler) line -> {
                if (line.contains("↑")) {
                    log.info(line);
                }
                lines.add(line);
                if (System.currentTimeMillis() - startTime > 1000 * 60 * 5) {
                    // 5分钟没有输出则认为上传失败
                    log.error("文件[{}]上传失败!", fileName);
                    process.destroy();
                }
            });
            if (!lines.isEmpty() && lines.get(lines.size() - 1).contains("上传结束")) {
                log.info("文件[{}]上传成功!", fileName);
                UPLOAD_FILES.add(fileName);
                FileUtil.writeUtf8Lines(UPLOAD_FILES, RECORD_FILE);
                FileUtil.del(zipPath);
            } else {
                log.error("文件[{}]上传失败!", fileName);
                for (String line : lines) {
                    log.error("文件[{}]上传信息:{}", fileName, line);
                    if (line.contains("31045")) {
                        // 登录过期逻辑
                        sendMail("百度上传异常", CollUtil.join(lines, System.lineSeparator()));
                        applicationContext.close();
                        return;
                    }
                    check31352(zipPath, remoteDatePath, line);
                }
            }
        }
    }
    
    private void check31352(String zipPath, String remoteDatePath, String line) {
        if (line.contains("31352")) {
            // 改名上传
            String mvCmd = "mv " + zipPath + " /tmp/test.zip";
            log.info("改名:[{}]", mvCmd);
            RuntimeUtil.execForStr(mvCmd);
            String upCmd = EXEC + " upload /tmp/test.zip" + " " + remoteDatePath;
            log.info("上传:[{}]", upCmd);
            RuntimeUtil.execForStr(upCmd);
            String[] op = zipPath.split("/");
            String zipName = op[op.length - 1];
            String renameCmd = EXEC + " mv " + remoteDatePath + "test.zip" + " " + remoteDatePath + zipName;
            log.info("重命名:[{}]", renameCmd);
            RuntimeUtil.execForStr(renameCmd);
        }
    }
    
    @PostConstruct
    public void init() {
        List<String> files = FileUtil.readUtf8Lines(RECORD_FILE);
        UPLOAD_FILES.addAll(files);
    }
}
